//
// Created by 618lf on 2022/6/20.
//
#include "library.h"

#ifdef UNIX
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <grp.h>
#include <jni.h>
#include <pwd.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#if !(defined(__FreeBSD__) || defined(__MACH__))
#include <sys/sendfile.h>
#endif
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#endif

#ifdef WINDOWS

#include <assert.h>

#endif

#include "errno_enum.h"

// the NativeIOException class and its constructor
static jclass nioe_clazz;
static jmethodID nioe_ctor;

/*
 * Throw a java.IO.IOException, generating the message from errno.
 * NB. this is also used form windows_secure_container_executor.c
 */
extern void throw_ioe(JNIEnv *env, int errnum);

static void nioe_init(JNIEnv *env) {
    // Init NativeIOException
    nioe_clazz = (*env)->FindClass(
            env, "com/sample/jni/NativeIOException");
    PASS_EXCEPTIONS(env);

    nioe_clazz = (*env)->NewGlobalRef(env, nioe_clazz);
#ifdef UNIX
    nioe_ctor = (*env)->GetMethodID(env, nioe_clazz, "<init>",
    "(Ljava/lang/String;Lcom/sample/jni/Errno;)V");
#endif

#ifdef WINDOWS
    nioe_ctor = (*env)->GetMethodID(env, nioe_clazz, "<init>",
                                    "(Ljava/lang/String;I)V");
#endif
}

static void nioe_deinit(JNIEnv *env) {
    if (nioe_clazz != NULL) {
        (*env)->DeleteGlobalRef(env, nioe_clazz);
        nioe_clazz = NULL;
    }
    nioe_ctor = NULL;
}

/*
 * Class:     com_sample_jni_HelloService
 * Method:    initNative
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_sample_jni_HelloService_initNative
        (JNIEnv *env, jclass clazz) {
    nioe_init(env);
    PASS_EXCEPTIONS_GOTO(env, error);
#ifdef UNIX
    errno_enum_init(env);
    PASS_EXCEPTIONS_GOTO(env, error);
#endif
    return;
    error:
    // these are all idempodent and safe to call even if the
    // class wasn't initted yet
    nioe_deinit(env);
#ifdef UNIX
    errno_enum_deinit(env);
#endif
}

/*
 * Class:     com_sample_jni_HelloService
 * Method:    mlock_native
 * Signature: (Ljava/nio/ByteBuffer;J)V
 */
JNIEXPORT void JNICALL Java_com_sample_jni_HelloService_mlock_1native
        (JNIEnv *env, jclass clazz, jobject buffer, jlong len) {
    void *buf = (void *) (*env)->GetDirectBufferAddress(env, buffer);
    PASS_EXCEPTIONS(env);
#ifdef UNIX
    if (mlock(buf, len)) {
        CHECK_DIRECT_BUFFER_ADDRESS(buf);
        throw_ioe(env, errno);
    }
#endif
#ifdef WINDOWS
    if (!VirtualLock(buf, len)) {
        CHECK_DIRECT_BUFFER_ADDRESS(buf);
        throw_ioe(env, GetLastError());
    }
#endif
}

/*
 * Throw a java.IO.IOException, generating the message from errno.
 */
void throw_ioe(JNIEnv *env, int errnum) {
#ifdef UNIX
    char message[80];
    jstring jstr_message;

    snprintf(message,sizeof(message),"%s",terror(errnum));

    jobject errno_obj = errno_to_enum(env, errnum);

    if ((jstr_message = (*env)->NewStringUTF(env, message)) == NULL)
      goto err;

    jthrowable obj = (jthrowable)(*env)->NewObject(env, nioe_clazz, nioe_ctor,
      jstr_message, errno_obj);
    if (obj == NULL) goto err;

    (*env)->Throw(env, obj);
    return;
  err:
    if (jstr_message != NULL)
      (*env)->ReleaseStringUTFChars(env, jstr_message, message);
#endif

#ifdef WINDOWS
    DWORD len = 0;
    LPWSTR buffer = NULL;
    const jchar *message = NULL;
    jstring jstr_message = NULL;
    jthrowable obj = NULL;

    len = FormatMessageW(
            FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
            NULL, *(DWORD *) (&errnum), // reinterpret cast
            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
            (LPWSTR) &buffer, 0, NULL);

    if (len > 0) {
        message = (const jchar *) buffer;
    } else {
        message = (const jchar *) L"Unknown error.";
    }

    if ((jstr_message = (*env)->NewString(env, message, len)) == NULL)
        goto err;
    LocalFree(buffer);
    buffer = NULL; // Set buffer to NULL to avoid double free

    obj = (jthrowable) (*env)->NewObject(env, nioe_clazz, nioe_ctor,
                                         jstr_message, errnum);
    if (obj == NULL) goto err;

    (*env)->Throw(env, obj);
    return;

    err:
    if (jstr_message != NULL)
        (*env)->ReleaseStringChars(env, jstr_message, message);
    LocalFree(buffer);
    return;
#endif
}